package taskhandler

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"cvevulner/common"
	"cvevulner/models"

	"github.com/astaxie/beego/logs"
)

type CvrfSa struct {
	XMLName            xml.Name            `xml:"cvrfdoc,omitempty"`
	Xmlns              string              `xml:"xmlns,attr"`
	XmlnsCvrf          string              `xml:"xmlns:cvrf,attr"`
	DocumentTitle      *DocumentTitle      `xml:"DocumentTitle,omitempty"`
	DocumentType       string              `xml:"DocumentType"`
	DocumentPublisher  *DocumentPublisher  `xml:"DocumentPublisher,omitempty"`
	DocumentTracking   *DocumentTracking   `xml:"DocumentTracking,omitempty"`
	DocumentNotes      *DocumentNotes      `xml:"DocumentNotes,omitempty"`
	DocumentReferences *DocumentReferences `xml:"DocumentReferences,omitempty"`
	ProductTree        *ProductTree        `xml:"ProductTree,omitempty"`
	Vulnerability      []Vulnerability     `xml:"Vulnerability,omitempty"`
}

type DocumentTitle struct {
	XMLName       xml.Name `xml:"DocumentTitle,omitempty"`
	XmlLang       string   `xml:"xml:lang,attr"`
	DocumentTitle string   `xml:",innerxml"`
}

type DocumentPublisher struct {
	XMLName          xml.Name `xml:"DocumentPublisher,omitempty"`
	Type             string   `xml:"Type,attr"`
	ContactDetails   string   `xml:"ContactDetails"`
	IssuingAuthority string   `xml:"IssuingAuthority"`
}

type DocumentTracking struct {
	XMLName            xml.Name         `xml:"DocumentTracking,omitempty"`
	Identification     *Identification  `xml:"Identification,omitempty"`
	Status             string           `xml:"Status"`
	Version            string           `xml:"Version"`
	RevisionHistory    *RevisionHistory `xml:"RevisionHistory,omitempty"`
	InitialReleaseDate string           `xml:"InitialReleaseDate"`
	CurrentReleaseDate string           `xml:"CurrentReleaseDate"`
	Generator          *Generator       `xml:"Generator,omitempty"`
}

type Identification struct {
	XMLName xml.Name `xml:"Identification,omitempty"`
	Id      string   `xml:"ID"`
}

type RevisionHistory struct {
	XMLName  xml.Name   `xml:"RevisionHistory,omitempty"`
	Revision []Revision `xml:"Revision,omitempty"`
}

type Revision struct {
	XMLName     xml.Name `xml:"Revision,omitempty"`
	Number      string   `xml:"Number"`
	Date        string   `xml:"Date"`
	Description string   `xml:"Description"`
}

type Generator struct {
	XMLName xml.Name `xml:"Generator,omitempty"`
	Engine  string   `xml:"Engine"`
	Date    string   `xml:"Date"`
}

type DocumentNotes struct {
	XMLName xml.Name `xml:"DocumentNotes,omitempty"`
	Note    []Note   `xml:"Note,omitempty"`
}

type Note struct {
	XMLName xml.Name `xml:"Note,omitempty"`
	Title   string   `xml:"Title,attr"`
	Type    string   `xml:"Type,attr"`
	Ordinal string   `xml:"Ordinal,attr"`
	XmlLang string   `xml:"xml:lang,attr"`
	Note    string   `xml:",innerxml"`
}

type DocumentReferences struct {
	XMLName      xml.Name       `xml:"DocumentReferences,omitempty"`
	CveReference []CveReference `xml:"Reference,omitempty"`
}

type CveReference struct {
	XMLName xml.Name `xml:"Reference,omitempty"`
	Type    string   `xml:"Type,attr"`
	CveUrl  []CveUrl `xml:"URL,omitempty"`
}

type CveUrl struct {
	XMLName xml.Name `xml:"URL,omitempty"`
	Url     string   `xml:",innerxml"`
}

type ProductTree struct {
	XMLName         xml.Name          `xml:"ProductTree,omitempty"`
	Xmlns           string            `xml:"xmlns,attr"`
	OpenEulerBranch []OpenEulerBranch `xml:"Branch,omitempty"`
}

type OpenEulerBranch struct {
	XMLName         xml.Name          `xml:"Branch,omitempty"`
	Type            string            `xml:"Type,attr"`
	Name            string            `xml:"Name,attr"`
	FullProductName []FullProductName `xml:"FullProductName,omitempty"`
}

type FullProductName struct {
	XMLName         xml.Name `xml:"FullProductName,omitempty"`
	ProductId       string   `xml:"ProductID,attr"`
	Cpe             string   `xml:"CPE,attr"`
	FullProductName string   `xml:",innerxml"`
}

type Vulnerability struct {
	XMLName         xml.Name         `xml:"Vulnerability,omitempty"`
	Ordinal         string           `xml:"Ordinal,attr"`
	Xmlns           string           `xml:"xmlns,attr"`
	CveNotes        *CveNotes        `xml:"Notes,omitempty"`
	ReleaseDate     string           `xml:"ReleaseDate"`
	Cve             string           `xml:"CVE"`
	ProductStatuses *ProductStatuses `xml:"ProductStatuses,omitempty"`
	Threats         *Threats         `xml:"Threats,omitempty"`
	CvssScoreSets   *CVSSScoreSets   `xml:"CVSSScoreSets,omitempty"`
	Remediations    *Remediations    `xml:"Remediations,omitempty"`
}

type CveNotes struct {
	XMLName xml.Name `xml:"Notes,omitempty"`
	CveNote *CveNote `xml:"Note,omitempty"`
}

type CveNote struct {
	XMLName xml.Name `xml:"Note,omitempty"`
	Title   string   `xml:"Title,attr"`
	Type    string   `xml:"Type,attr"`
	Ordinal string   `xml:"Ordinal,attr"`
	XmlLnag string   `xml:"xml:lang,attr"`
	Note    string   `xml:",innerxml"`
}

type ProductStatuses struct {
	XMLName xml.Name  `xml:"ProductStatuses,omitempty"`
	Status  []*Status `xml:"Status,omitempty"`
}

type Status struct {
	XMLName   xml.Name    `xml:"Status,omitempty"`
	Type      string      `xml:"Type,attr"`
	ProductId []ProductId `xml:"ProductID,omitempty"`
}

type ProductId struct {
	XMLName   xml.Name `xml:"ProductID,omitempty"`
	ProductId string   `xml:",innerxml"`
}

type Threats struct {
	XMLName xml.Name `xml:"Threats,omitempty"`
	Threat  *Threat  `xml:"Threat,omitempty"`
}

type Threat struct {
	XMLName     xml.Name `xml:"Threat,omitempty"`
	Type        string   `xml:"Type,attr"`
	Description string   `xml:"Description"`
}

type CVSSScoreSets struct {
	XMLName  xml.Name  `xml:"CVSSScoreSets,omitempty"`
	ScoreSet *ScoreSet `xml:"ScoreSet,omitempty"`
}

type ScoreSet struct {
	XMLName   xml.Name `xml:"ScoreSet,omitempty"`
	BaseScore string   `xml:"BaseScore"`
	Vector    string   `xml:"Vector"`
}

type Remediations struct {
	XMLName     xml.Name     `xml:"Remediations,omitempty"`
	Remediation *Remediation `xml:"Remediation,omitempty"`
}

type UnRemediations struct {
	XMLName     xml.Name        `xml:"Remediations,omitempty"`
	Remediation []UnRemediation `xml:"Remediation,omitempty"`
}

type Remediation struct {
	XMLName     xml.Name `xml:"Remediation,omitempty"`
	Type        string   `xml:"Type,attr"`
	Description string   `xml:"Description"`
	Date        string   `xml:"DATE"`
	Url         string   `xml:"URL"`
}

type UnRemediation struct {
	XMLName     xml.Name `xml:"Remediation,omitempty"`
	Type        string   `xml:"Type,attr"`
	Description string   `xml:"Description"`
	Date        string   `xml:"DATE"`
	ProductId   string   `xml:"ProductID"`
	Reason      string   `xml:"Reason,omitempty"`
}

type CveInfo struct {
	CveNum            string
	Description       string
	ProductID         string
	CveLevel          string
	BaseScore         float64
	Vector            string
	Summary           string
	OpenEulerSANumUrl string
}

// Intermediate information
type SecurityNoticeCvrf struct {
	CveNum        string
	Summary       string
	Theme         string
	ReferenceLink string
	Description   string
	Introduction  string
	CveLevel      string
	BaseScore     float64
}

type PackRpmCvrf struct {
	PackName string
}

type CveCvrf struct {
	Introduction   string
	OpenEulerSANum string
	PublicDate     string
	CveLevel       string
	OwnedComponent string
	SecurityNotice map[string][]SecurityNoticeCvrf
	PackRpmx       map[string][]PackRpm
	CveInfo        CveInfo
}

type BrachCveInfo struct {
	OpenEulerSANum string
	CvrfFileName   string
	CveNumSlice    []string
	OpenEulerScore []float64
}

type ComponentInfo struct {
	OpenEulerSANum string
	OwnedComponent string
	CveNum         []string
	CveNumMap      map[string]BrachCveInfo
	OpenEulerScore []float64
	UpdateFlag     int
	CvrfFileName   string
}

type UnaffectCvrfSa struct {
	XMLName       xml.Name                `xml:"cvrfdoc,omitempty"`
	Xmlns         string                  `xml:"xmlns,attr"`
	XmlnsCvrf     string                  `xml:"xmlns:cvrf,attr"`
	Vulnerability []UnaffectVulnerability `xml:"Vulnerability,omitempty"`
}

type UnaffectVulnerability struct {
	XMLName         xml.Name         `xml:"Vulnerability,omitempty"`
	Ordinal         string           `xml:"Ordinal,attr"`
	Xmlns           string           `xml:"xmlns,attr"`
	CveNotes        *CveNotes        `xml:"Notes,omitempty"`
	Cve             string           `xml:"CVE"`
	Threats         *Threats         `xml:"Threats"`
	ProductStatuses *ProductStatuses `xml:"ProductStatuses,omitempty"`
	CvssScoreSets   *CVSSScoreSets   `xml:"CVSSScoreSets,omitempty"`
	Remediations    *UnRemediations  `xml:"Remediations,omitempty"`
}

func BuildUnaffectVulnerabilitySet(unaffectCvrfsa *UnaffectCvrfSa, v models.ExcelExport,
	affectBranch string, componentMap map[string]ComponentInfo) {
	vulnerability := make([]UnaffectVulnerability, 0)
	vulnerabilityx := make([]UnaffectVulnerability, 0)
	if len(unaffectCvrfsa.Vulnerability) > 0 {
		vulnerabilityx = BuildUnaffVulnerabilitySlice(unaffectCvrfsa.Vulnerability, v, affectBranch, componentMap)
	} else {
		vulnerabilityx = BuildUnaffVulnerabilitySlice(vulnerability, v, affectBranch, componentMap)
	}
	unaffectCvrfsa.Vulnerability = vulnerabilityx
}

func BuildUnaffVulnerabilitySlice(vulnerability []UnaffectVulnerability, v models.ExcelExport,
	affectBranch string, componentMap map[string]ComponentInfo) []UnaffectVulnerability {
	cpe := affectBranch
	affectType := v.AffectType(cpe)
	if vulnerability != nil && len(vulnerability) > 0 {
		cveExist := false
		for i, vl := range vulnerability {
			if vl.Cve == v.CveNum && vl.Remediations != nil && len(vl.Remediations.Remediation) > 0 &&
				vl.Remediations.Remediation[0].Description == v.InfluenceComponent {
				statusTypeExist := false
				cpeExist := false

				for si, status := range vl.ProductStatuses.Status {
					if status.Type == affectType {
						statusTypeExist = true
						for _, pid := range status.ProductId {
							if pid.ProductId == cpe {
								cpeExist = true
								break
							}
						}

						if !cpeExist {
							var productId ProductId
							productId.ProductId = cpe
							vl.ProductStatuses.Status[si].ProductId = append(vl.ProductStatuses.Status[si].ProductId, productId)
							var remediation UnRemediation
							remediation.Type = status.Type
							remediation.Description = v.InfluenceComponent
							remediation.Date = common.GetCurDate()
							remediation.ProductId = cpe
							remediation.Reason = v.GetReasonByVersion(cpe)
							vl.Remediations.Remediation = append(vl.Remediations.Remediation, remediation)
							vulnerability[i] = vl
						}
					}
				}

				if !statusTypeExist {
					var status Status
					status.Type = affectType
					var productId ProductId
					productId.ProductId = cpe
					productIdSlice := make([]ProductId, 0)
					productIdSlice = append(productIdSlice, productId)
					status.ProductId = productIdSlice
					vl.ProductStatuses.Status = append(vl.ProductStatuses.Status, &status)
					var remediation UnRemediation
					remediation.Type = status.Type
					remediation.Description = v.InfluenceComponent
					remediation.Date = common.GetCurDate()
					remediation.ProductId = cpe
					remediation.Reason = v.GetReasonByVersion(cpe)
					vl.Remediations.Remediation = append(vl.Remediations.Remediation, remediation)
					vulnerability[i] = vl
				}

				cveExist = true
				break
			}
		}
		if !cveExist {
			vlLenth := len(vulnerability) + 1
			vulnerabilitySlice := BuildUnaffVulnerability(vlLenth, v, componentMap, cpe)
			if len(vulnerabilitySlice) > 0 {
				vulnerability = append(vulnerability, vulnerabilitySlice...)
			}
		}
	} else {
		vlLenth := 1
		vulnerabilitySlice := BuildUnaffVulnerability(vlLenth, v, componentMap, cpe)
		vulnerability = append(vulnerability, vulnerabilitySlice...)
	}
	return vulnerability
}

func BuildUnaffVulnerability(vlLenth int, v models.ExcelExport,
	componentMap map[string]ComponentInfo, cpe string) []UnaffectVulnerability {
	vulnerabilitySlice := make([]UnaffectVulnerability, 0)

	affectType := v.AffectType(cpe)

	var vulnerability UnaffectVulnerability
	vulnerability.Xmlns = "http://www.icasi.org/CVRF/schema/vuln/1.1"
	vulnerability.Ordinal = strconv.Itoa(vlLenth)
	var cveNotes CveNotes
	var cveNote CveNote
	cveNote.Ordinal = strconv.Itoa(vlLenth)
	cveNote.Type = "General"
	cveNote.Title = "Vulnerability Description"
	cveBrief := XmlSpecCharHand(v.CveBrief)
	cveNote.Note = cveBrief
	cveNote.XmlLnag = "en"
	cveNotes.CveNote = &cveNote
	vulnerability.CveNotes = &cveNotes
	vulnerability.Cve = v.CveNum

	vulnerability.Threats = &Threats{Threat: &Threat{Type: "Impact", Description: v.CveLevel}}

	var productStatuses ProductStatuses
	var status Status
	status.Type = affectType
	var productId ProductId
	productId.ProductId = cpe
	productIdSlice := make([]ProductId, 0)
	productIdSlice = append(productIdSlice, productId)
	status.ProductId = productIdSlice
	productStatuses.Status = append(productStatuses.Status, &status)
	vulnerability.ProductStatuses = &productStatuses
	var cVSSScoreSets CVSSScoreSets
	var scoreSet ScoreSet
	scoreSet.BaseScore = fmt.Sprintf("%.1f", v.OpeneulerScore)
	scoreSet.Vector = v.OpeneulerVector
	cVSSScoreSets.ScoreSet = &scoreSet
	vulnerability.CvssScoreSets = &cVSSScoreSets
	var remediations UnRemediations
	remediationSlice := make([]UnRemediation, 0)
	var remediation UnRemediation
	remediation.Type = affectType
	remediation.Description = v.InfluenceComponent
	remediation.Date = common.GetCurDate()
	remediation.ProductId = cpe
	remediation.Reason = v.GetReasonByVersion(cpe)
	remediationSlice = append(remediationSlice, remediation)
	remediations.Remediation = remediationSlice
	vulnerability.Remediations = &remediations
	vulnerabilitySlice = append(vulnerabilitySlice, vulnerability)
	return vulnerabilitySlice
}

// File Upload
func PostFile(filename string, targetUrl string) error {
	upfileErr := ObsUploadFile(targetUrl, filename)
	if upfileErr != nil {
		logs.Error("upfileErr: ", upfileErr)
	}
	return upfileErr
}

type ReplaceHelper struct {
	OldPath string
	NewPath string
	OldText string
	NewText string
}

func (h *ReplaceHelper) DoWrok() error {
	return filepath.Walk(h.OldPath, h.walkCallback)
}

func (h ReplaceHelper) walkCallback(path string, f os.FileInfo, err error) error {
	if err != nil {
		return err
	}
	if f == nil {
		return nil
	}
	if f.IsDir() {
		return nil
	}
	buf, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	content := string(buf)
	newContent := strings.Replace(content, h.OldText, h.NewText, -1)
	ioutil.WriteFile(h.NewPath, []byte(newContent), os.ModePerm)
	return err
}
